package com.zdxlz.fcmp.common.security.filter;

import com.zdxlz.fcmp.common.core.constant.CommonConstants;
import com.zdxlz.fcmp.common.core.constant.enums.ResCode;
import com.zdxlz.fcmp.common.core.exception.BizException;
import com.zdxlz.fcmp.common.security.service.RedisAuthServiceImpl;
import com.zdxlz.fcmp.common.security.util.JwtTokenUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * @className JwtAuthenticationTokenFilter
 * @description  jwt认证过滤器，用于验证请求头中的TOKEN。
 *
 * @author: aman
 * @date 2025/1/30 16:23
 */
@Component
@Slf4j
@AllArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private final JwtTokenUtil jwtTokenUtil;
    private final RedisAuthServiceImpl redisAuthService;
    private final UserDetailsService userDetailsService;
    @Qualifier("excludeMatcher")
    private final RequestMatcher excludeMatcher;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求头中的TOKEN
        final String requestTokenHeader = request.getHeader(CommonConstants.AUTH_FIELD);
        if (requestTokenHeader == null) {
            handleError("TOKEN缺失", ResCode.ERR_AUTH_LACK);
            return;
        }

        // 验证TOKEN格式
        if (!requestTokenHeader.startsWith(CommonConstants.AUTH_TOKEN_STRAT)) {
            handleError("TOKEN数据格式不合法", ResCode.ERR_AUTH_INVALID);
            return;
        }

        String jwtToken = requestTokenHeader.substring(7);
        if (jwtTokenUtil.isTokenExpired(jwtToken)) {
            handleError("TOKEN已过期", ResCode.ERR_AUTH_EXPIRE);
        }

        String mobile = jwtTokenUtil.parseToken(jwtToken);
        // 验证TOKEN是否合法
        if (mobile == null) {
            handleError("TOKEN不合法", ResCode.ERR_AUTH_INVALID);
            return;
        }
        Long versionAccessToken = redisAuthService.getVersionAccessToken(mobile, jwtToken);
        // 验证TOKEN是否存在于Redis
        if (null == versionAccessToken) {
            handleError("TOKEN不存在于Redis", ResCode.ERR_AUTH_INVALID);
            return;
        }

        // 验证TOKEN版本号是否匹配
        if (!isTokenVersionValid(mobile, versionAccessToken)) {
            return;
        }

        // 设置用户认证信息
        authenticateUser(request, mobile);

        // 继续过滤链
        filterChain.doFilter(request, response);
    }


    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        log.info("请求地址：{}",request.getRequestURI());
        return excludeMatcher.matches(request);
    }

    private void handleError(String logMessage, ResCode resCode) {
        log.error(logMessage);
        throw new BizException(resCode.getCode(), resCode.getReason());
    }

    private boolean isTokenVersionValid(String mobile, Long tokenVersion) {
        Long version = redisAuthService.getUserVersion(mobile);

        if (!tokenVersion.equals(version)) {
            handleError(String.format("TOKEN不合法，版本信息错误，Redis用户版本【%d】，Token数据版本【%d】", version, tokenVersion), ResCode.ERR_AUTH_INVALID);
        }
        return true;
    }

    private void authenticateUser(HttpServletRequest request, String mobile) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

}
